#include <mm/paging.h>
#include <mm/kmalloc.h>
#include <utils/debug.h>
#include <utils/bitmap.h>

struct page_directory *current_directory;

// keep track of used frames
bitmap *frames = 0;

void paging_init(uint32 mem_end) {
    frames = bitmap_new(mem_end / PAGE_TABLE_SIZE);
}

void alloc_frame(struct page *p, uint8 flags) {
    assert(frames && "Paging has not been initialized");
    
    if(!p->frame) {
        uint32 frame = bitmap_get_first_available(frames);
        assert(frame != (uint32)-1 && "Out of memory");
        bitmap_set(frames, frame);
        p->present = 1;
        p->rw = flags & WRITEABLE ? 1 : 0;
        p->user = flags & KERNEL ? 1 : 0;
        p->frame = frame;
    }
}

void free_frame(struct page *p) {
    if (p->frame) {
        bitmap_clear(frames, p->frame);
        p->frame = 0;
    }
}

// External interface
void set_page_directory(struct page_directory *dir) {
    current_directory = dir;
    
    asm volatile("mov %0, %%cr3":: "r"(dir->physical_address));
    uint32 cr0;
    asm volatile("mov %%cr0, %0" : "=r"(cr0));
    cr0 |= 0x80000000;
    asm volatile("mov %0, %%cr0" :: "r"(cr0));
}

struct page* get_page(struct page_directory *dir, uint32 address) {
    address /= PAGE_SIZE;

    uint32 table_idx = address / PAGE_TABLE_SIZE;

    if(dir->tables[table_idx]) {
        return &dir->tables[table_idx]->pages[address % PAGE_TABLE_SIZE];
    }
    return 0;
}

struct page* new_page(struct page_directory *dir, uint32 address, uint8 flags) {
    address /= PAGE_SIZE;
    //ENSURE_PAGE_ALIGNED(address);

    uint32 table_idx = address / PAGE_TABLE_SIZE;
    if(!dir->tables[table_idx]) {
        uint32 tmp;
        dir->tables[table_idx] = (struct page_table*)kmallocpa(sizeof(struct page_table), (void**)&tmp);
#ifdef DEBUG_MALLOC
        kprintf("new page: %x\n", tmp);
#endif
        dir->tables_addresses[table_idx] = tmp | flags;
        return &dir->tables[table_idx]->pages[address % PAGE_TABLE_SIZE];
    }
    return 0;
}

